<?php
class Queue extends \SplMinHeap
{
    public function compare($value1, $value2)
    {
        return $value1->ct < $value2->ct ? 1 : ($value1->ct == $value2->ct ? 0 : -1);
    }
}

class Task
{
    public $ct;
    protected $cb;

    public function __construct($call_time, callable $callback)
    {
        $this->ct = $call_time;
        $this->cb = $callback;
    }

    public function run()
    {
        return call_user_func_array($this->cb, func_get_args());
    }
}

class PeriodicTask extends Task
{
    protected $interval;

    public function __construct($interval, callable $callback)
    {
        $this->interval = $interval;
        parent::__construct((int)gettimeofday(true) * 1000 + $interval, $callback);
    }

    public function nextTime()
    {
        $this->ct += $this->interval;
        return $this;
    }
}

class Timer
{
    /** @var Queue */
    private $queue;

    public function __construct()
    {
        $this->queue = new Queue();
    }

    public function isEmpty()
    {
        return $this->queue->isEmpty();
    }

    public function setTimeOut($callback, $timeout)
    {
        $task = new Task((int)gettimeofday(true) * 1000 + $timeout, $callback);
        $this->queue->insert($task);
        return $task;
    }

    public function setInterval($callback, $interval)
    {
        $task = new PeriodicTask($interval, $callback);
        $this->queue->insert($task);
        return $task;
    }

    public function nearestTaskTime()
    {
        return $this->isEmpty() ? null : $this->queue->top()->ct;
    }

    public function nearestTaskDeltaTime()
    {
        return $this->nearestTaskTime() - (int)gettimeofday(true) * 1000;
    }

    public function tick()
    {
        $now = (int)gettimeofday(true) * 1000;
        $count = $this->queue->count();
        $i = 0;

        while (!$this->isEmpty()) {
            if ($i++ > $count) {
                break;
            }
            $task = $this->queue->top();
            if ($task->ct <= $now) {
                $task = $this->queue->extract();
                $task->run();
                if ($task instanceof PeriodicTask) {
                    $this->queue->insert($task->nextTime());
                }
            } else {
                break;
            }
        }
    }

    public function clear()
    {
        $this->queue = new Queue();
        return $this;
    }
}

function testTimer()
{
    $timer = new Timer();

    $timer->setInterval(function () {
        echo "a\n";
    }, 1000);
    $timer->setInterval(function () {
        echo "b\n";
    }, 2000);
    $timer->setInterval(function () {
        echo "c\n";
    }, 3000);

    while (1) {
        $timer->tick();
        yield 1;
        usleep(4000);
    }
}

$timer = testTimer();
while ($cur = $timer->current()) {
    echo $cur, "----------------------\n";
    $timer->next();
}
